練習完文字效果後我打算測試導入模型。
事不宜遲,借一下自己快兩年前用Blender練習捏的動漫風人頭。
一開始導入只有半張臉,趕快apply鏡像修改器,尷尬。
說到3D模型的檔案格式,如果逛過Sketchfab,大概會知道有fbx, obj云云。而這兩者分別適用動態和靜態模型。不過相較於它們,其實GLTF和GLB更適合網路環境。前者更是採用json格式。
熟悉的Poimandres再次佛心來著,開發者Sara Vieira直接做了個GLTF轉換器。
把模型丟上去,無痛產出可用程式碼,直接拿來改就行。
光預設就給模型猶如旋轉地台的展示空間,感人。
後來看到別的案例,不只GLTF,GLB也可以拿來轉換喔。
進入實作後,我想提升模型的自轉速度。
這裡不是用speed,而是用autoRotateSpeed={8}去改數值。
調鏡頭焦距則是用adjustCamera,值越大就zoom越遠。
到這有點太容易了,當然要挑戰更多。來模擬相機顫抖吧。
等等,這聽起來不是Blender, AE或UE才做得到嗎?
但drei的<CameraShake />
就是這麼優雅。
不過我還想要讓模型持續旋轉,但直接搭配不用動腦的autoRate會使整個模型轉出鏡頭外。因為整個stage在公轉。
於是沿用useRef + useFrame的旋轉手法。
然而有個似乎無傷大雅,卻讓人渾身不對勁的問題是:點進網頁後的剛開始,整個畫面會抖一下。
把<CameraShake />
也包在suspense裡解決狀況後,又用background-image覆蓋上一層代表相機畫面的svg。心中暗叫:很有感覺。
再來可以做什麼呢?我想還原模型的材質。
本以為依靠glb會很順利,結果沒效還變灰階。
加上寫這段code的那晚,還遇到codesandbox出bug。一整個精神緊張。
所幸最終發現是Blender Node能支援匯出的東西很少。只好試一下烘焙貼圖。
別忘了先unwrap一波,配合image texture即可熱騰騰地產出。
據官方指南記載,在Node有用到Bump的話,插值法選cubic比較好。雖然我是看不太出差別啦……還是先依樣bake。
大概凌晨一點半左右,codesandbox也恢復了。好在模型終於展現應有的質感。
//App.js
import { Suspense, useRef } from "react";
import { Canvas } from "@react-three/fiber";
import { CameraShake, Stage } from "@react-three/drei";
import { Model } from "./Model";
export default function Viewer() {
const ref = useRef();
return (
<Canvas shadows dpr={[1, 2]} camera={{ fov: 50 }}>
<Suspense fallback={null}>
<Stage
controls={ref}
preset="rembrandt"
intensity={1}
environment="city"
adjustCamera={1.5}
>
false
<Model />
false
</Stage>
<CameraShake
maxYaw={(Math.random() - 0.5) / 2}
maxPitch={(Math.random() - 0.5) / 2}
maxRoll={(Math.random() - 0.5) / 2}
yawFrequency={0.1}
pitchFrequency={0.1}
rollFrequency={0.1}
intensity={1}
decayRate={0.65}
/>
</Suspense>
</Canvas>
);
}
//Model.js
import React, { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import { useGLTF } from "@react-three/drei";
export function Model(props) {
const ref = useRef();
useFrame(() => {
ref.current.rotation.y += 0.03;
});
const { nodes, materials } = useGLTF("./face.glb");
return (
<group {...props} dispose={null} ref={ref}>
<mesh
castShadow
receiveShadow
geometry={nodes.Model.geometry}
material={materials.Material}
/>
</group>
);
}
useGLTF.preload("./face.glb");